# @Version        : 1.0
# @Update Time    : 2024/12/9 22:29
# @File           : operation_record_middleware.py
# @IDE            : PyCharm
# @Desc           : 操作记录中间件，用于记录用户操作到MongoDB数据库

import json
import time
from typing import Dict, Optional

from fastapi import Request, Response
from fastapi.routing import APIRoute
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from user_agents import parse

from applications import settings
from applications.utils.xdb_searcher import XdbSearcher
from applications.core.context_manager.track_utils import TrackContextUtils
from applications.log import logger
from applications.models.admin_log import OperationLog


class OperationRecordMiddleware(BaseHTTPMiddleware):
    """
    操作记录中间件

    用于记录用户操作到MongoDB数据库，包括：
    - 请求参数
    - 响应状态
    - 执行时间
    - 用户信息
    - 客户端信息
    """

    async def _get_request_body(self, request: Request) -> Optional[str]:
        """
        获取请求体内容

        Args:
            request: FastAPI请求对象

        Returns:
            Optional[str]: 请求体内容，如果是文件上传则返回None
        """
        content_type = request.headers.get("content-type", "")
        if "multipart/form-data" in content_type:
            return None

        try:
            body = await request.body()
            return body.decode("utf-8", errors="ignore")
        except Exception as e:
            logger.error(f"解析请求体失败: {str(e)}")
            return None

    async def _should_record(self, request: Request, route: APIRoute) -> bool:
        """
        判断是否需要记录操作

        Args:
            request: FastAPI请求对象
            route: API路由对象

        Returns:
            bool: 是否需要记录
        """
        return request.method in settings.OPERATION_RECORD_METHOD and route.path not in settings.IGNORE_OPERATION_PATH

    async def _get_client_info(self, request: Request) -> Dict[str, str]:
        """
        获取客户端信息

        Args:
            request: FastAPI请求对象

        Returns:
            Dict[str, str]: 客户端信息字典
        """
        user_agent = parse(request.headers.get("user-agent", ""))
        return {
            "system": f"{user_agent.os.family} {user_agent.os.version_string}",
            "browser": f"{user_agent.browser.family} {user_agent.browser.version_string}",
            "client_ip": request.client.host if request.client else "unknown",
        }

    async def _create_operation_record(
        self,
        request: Request,
        response: Response,
        route: APIRoute,
        process_time: float,
        body_params: Optional[str],
        client_info: Dict[str, str],
    ):
        """
        创建操作记录文档

        Args:
            request: FastAPI请求对象
            response: FastAPI响应对象
            route: API路由对象
            process_time: 处理时间
            body_params: 请求体参数
            client_info: 客户端信息

        Returns:
            Dict[str, Any]: 操作记录文档
        """
        query_params = dict(request.query_params.multi_items())
        path_params = request.path_params
        user = request.state.user
        user_id = user.id if user is not None else None
        # ip地址解析。
        with XdbSearcher() as xdb_searcher:
            ip_region = xdb_searcher.search_region(client_info["client_ip"])
        operation_log = OperationLog(
            summary=route.summary,
            method=request.method,
            url=request.url.path,
            ip=client_info["client_ip"],
            user_id=user_id,
            request_id=TrackContextUtils.get_request_id(),
            params=json.dumps(
                {
                    "body_params": body_params,
                    "query_params": query_params if query_params else None,
                    "path_params": path_params if path_params else None,
                }
            ),
            cost_time=round(process_time, 2),
            status=response.status_code,
            browser=client_info["browser"],
            system=client_info["system"],
            route_name=route.name,
            content_length=response.headers.get("content-length", "0"),
            tags=str(route.tags),
            country=ip_region.country,
            city=ip_region.city,
            region=ip_region.region,
            province=ip_region.province,
            isp=ip_region.isp,
        )
        await operation_log.save()

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """
        中间件调度方法

        Args:
            request: FastAPI请求对象
            call_next: 下一个中间件或路由处理函数

        Returns:
            Response: FastAPI响应对象
        """

        start_time = time.perf_counter()

        # 获取请求体
        body_params = await self._get_request_body(request)

        # 执行请求
        response = await call_next(request)

        # 获取路由信息
        route = request.scope.get("route")
        if not isinstance(route, APIRoute):
            return response

        # 判断是否需要记录
        if not await self._should_record(request, route):
            return response

        try:
            # 获取处理时间
            process_time = time.perf_counter() - start_time

            # 获取客户端信息
            client_info = await self._get_client_info(request)

            # 创建操作记录
            await self._create_operation_record(
                request=request,
                response=response,
                route=route,
                process_time=process_time,
                body_params=body_params,
                client_info=client_info,
            )

            # 保存到数据库

        except Exception as e:
            logger.exception(e)
            logger.error(f"记录操作日志失败: {str(e)}")

        return response
